package agents;

import java.io.IOException;
import jade.util.leap.List;
import java.util.ArrayList;
import java.util.Iterator;
import model.Artifact;
import model.Offer;
import jade.lang.acl.*;
import jade.content.*;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.*;
import jade.content.lang.Codec.CodecException;
import jade.content.lang.sl.*;
import jade.core.*;
import jade.core.behaviours.*;
import jade.domain.*;
import jade.domain.mobility.*;
import jade.domain.JADEAgentManagement.*;

public class AuctioneerAgent extends Agent {

	private static final long serialVersionUID = -8100648081627624646L;
	private ArrayList<AID> currentParticipants;
	private boolean isClone = false;
	private Location home = null;
	private AID bossman = null;
	private Offer bestOffer;

	protected void beforeMove() {
		System.out.println(this.getLocalName() + ": Moving now to location: "
				+ this.getContainerController().getName());
	}

	protected void afterMove() {
		init();
		System.out.println(this.getLocalName() + ": Arrived at location: "
				+ this.getContainerController().getName());
		
		if(here().getID().equals(home.getID())) {
			try {
				reportToBossman();
			} catch (IOException e) {
				// Should not happen
				e.printStackTrace();
			}
		}

		else {
			// Find local auction participants
			addBehaviour(new FindParticipantsAndStartAuctionBehaviour());
		}
	}
	
	private void reportToBossman() throws IOException {
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
		msg.addReceiver(bossman);
		msg.setContentObject(bestOffer);
		msg.setLanguage(FIPANames.ContentLanguage.FIPA_SL);
		msg.setOntology(MobilityOntology.getInstance().getName());
		send(msg);
	}

	protected void beforeClone() {
		System.out.println(this.getLocalName() + ": beforeClone()");
	}

	protected void afterClone() {
		System.out.println(this.getLocalName() + ": afterClone()");
		init();
		isClone = true;

		// Perform a move depending on our name (LOL)
		addBehaviour(new OneShotBehaviour() {

			private static final long serialVersionUID = 8114113591924056594L;

			@Override
			public void action() {
				QueryPlatformLocationsAction query = new QueryPlatformLocationsAction();
				Action action = new Action(myAgent.getAID(), query);

				ACLMessage message = new ACLMessage(ACLMessage.REQUEST);
				message.addReceiver(myAgent.getAMS());
				message.setLanguage(FIPANames.ContentLanguage.FIPA_SL);
				message.setOntology(MobilityOntology.getInstance().getName());
				try {
					myAgent.getContentManager().fillContent(message, action);
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}

				myAgent.send(message);

				myAgent.addBehaviour(new listenForAMSReplyBehaviour());
			}
		});
	}

	private class listenForAMSReplyBehaviour extends OneShotBehaviour {

		private static final long serialVersionUID = -7813880910397127526L;

		public void action() {
			ACLMessage receivedMessage = myAgent
					.blockingReceive(MessageTemplate.MatchSender(myAgent
							.getAMS()));
			ContentElement content = null;
			try {
				content = myAgent.getContentManager().extractContent(
						receivedMessage);
			} catch (UngroundedException e) {
				e.printStackTrace();
			} catch (CodecException e) {
				e.printStackTrace();
			} catch (OntologyException e) {
				e.printStackTrace();
			}

			Result result = (Result) content;

			List listOfPlatforms = (List) result.getValue();

			String target = "";

			// Check where we should be going
			if (myAgent.getLocalName().indexOf("hm") != -1)
				// We are going to hm
				target = "hm";
			else
				target = "galileo";

			// use it
			
			Iterator<?> iter = (listOfPlatforms.iterator() instanceof Iterator<?>) ? (Iterator<?>) listOfPlatforms.iterator() : null;
			while (iter.hasNext()) {
				ContainerID next = (ContainerID) iter.next();
				if (next.getID().contains(target)) {
					System.out
							.println(myAgent.getLocalName()
									+ ": Found a container matching our target! Moving...");
					doMove(next);
				}
			}

		}
	}

	void init() {
		// Register language and ontology
		getContentManager().registerLanguage(new SLCodec());
		getContentManager().registerOntology(MobilityOntology.getInstance());
	}

	@Override
	protected void setup() {
		System.out.println(this.getLocalName() + ": Running setup()");
		currentParticipants = new ArrayList<AID>();
		home = here();
		bestOffer = new Offer();
		bossman = this.getAID();

		System.out.println(this.getLocalName() + ": Listening to clones responses");
		addBehaviour(new ListenToClonesBehaviour());

		// Make 2 clones
		addBehaviour(new CloneMyselfBehaviour("Auctioneer_hm_c"));
		addBehaviour(new CloneMyselfBehaviour("Auctioneer_galileo_c"));
	}
	
	private class ListenToClonesBehaviour extends CyclicBehaviour {

		private static final long serialVersionUID = -1302535762189919439L;
		
		private ArrayList<Offer> offers;
		
		public ListenToClonesBehaviour() {
			this.offers = new ArrayList<Offer>();
		}

		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.INFORM));
			if (msg != null) {
				if(msg.hasByteSequenceContent()) {
					Object o;
					try {
						o = msg.getContentObject();
						if(o instanceof Offer) {
							Offer of = (Offer) o;
							System.out.println(myAgent.getLocalName() + ": Received an offer: " + of);
							offers.add(of);
							
							// Do we have both our offers?
							if(offers.size() >= 2) {
								 System.out.println(myAgent.getLocalName() + ": Received all offers!");
								 int best = offers.get(0).compareTo(offers.get(1));
								 // offers.get(0) > offers.get(1)
								 if(best > 0) { 
									 System.err.println(myAgent.getLocalName() + ": Best offer received: " + offers.get(0));
									 }
								 // offers.get(0) < offers.get(1)
								 else if(best < 0) {
									 System.err.println(myAgent.getLocalName() + ": Best offer received: " + offers.get(1));
								 }
								 // offers.get(0) == offers.get(1)
								 else {
									 System.err.println(myAgent.getLocalName() + ": Best offer received: Both were equally good!");
								 }
								 
							}
						}
					} catch (UnreadableException e) {
						e.printStackTrace();
					}
					
				}
			}
			else {
				block();
			}
		}
	}

	private class CloneMyselfBehaviour extends OneShotBehaviour {

		private static final long serialVersionUID = -7001638026469199769L;

		private String tag;

		public CloneMyselfBehaviour(String tag) {
			this.tag = tag;
		}

		@Override
		public void action() {
			if (!isClone)
				doClone(myAgent.here(), tag);
		}

	}

	private class FindParticipantsAndStartAuctionBehaviour extends
			SequentialBehaviour {

		private static final long serialVersionUID = -6713383418434371673L;

		public FindParticipantsAndStartAuctionBehaviour() {
			// Clear the participants list
			currentParticipants.clear();
			doWait(200);
			addSubBehaviour(new FindAgentsInContainerBehaviour());
			addSubBehaviour(new ListenToAMSAndRequestParticipantsBehaviour());
			scheduleFirst();
		}
	}

	private class FindAgentsInContainerBehaviour extends OneShotBehaviour {

		private static final long serialVersionUID = -4064799466144867447L;

		@Override
		public void action() {
			QueryAgentsOnLocation qal = new QueryAgentsOnLocation();
			qal.setLocation(here());
			Action action = new Action(myAgent.getAID(), qal);
			
		    ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
		    msg.addReceiver(myAgent.getAMS());
		    msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
		    msg.setLanguage(FIPANames.ContentLanguage.FIPA_SL);
		    msg.setOntology(MobilityOntology.getInstance().getName());
		    
		    try {
				myAgent.getContentManager().fillContent(msg, action);
			} catch (CodecException e) {
				e.printStackTrace();
			} catch (OntologyException e) {
				e.printStackTrace();
			}
		    
		    myAgent.send(msg);
		}
	}
	
	private class ListenToAMSAndRequestParticipantsBehaviour extends OneShotBehaviour {

		private static final long serialVersionUID = -4064799466144867447L;

		@Override
		public void action() {
			ACLMessage receivedMessage = myAgent
					.blockingReceive(MessageTemplate.MatchSender(myAgent
							.getAMS()));
			
			ContentElement content = null;
			try {
				content = myAgent.getContentManager().extractContent(
						receivedMessage);
			} catch (UngroundedException e) {
				e.printStackTrace();
			} catch (CodecException e) {
				e.printStackTrace();
			} catch (OntologyException e) {
				e.printStackTrace();
			}
			
			ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
			msg.setContent("Auction?");
			
			Result result = (Result) content;
			List listOfAgents = (List) result.getValue();
			@SuppressWarnings("rawtypes")
			Iterator it = listOfAgents.iterator();
			while (it.hasNext()) {
				AID aid = (AID) it.next();
				msg.addReceiver(aid);
			}
				
			myAgent.send(msg);
			myAgent.addBehaviour(new ListenToParticipantResponsesBehaviour());
		}
	}
	
	private class ListenToParticipantResponsesBehaviour extends Behaviour {

		private static final long serialVersionUID = -7111220718331911118L;
		private final int WAIT_TIME_MILLIS = 2000;
		private final long startTime = System.currentTimeMillis();

		@Override
		public void action() {
			ACLMessage msg = myAgent.receive(MessageTemplate
					.MatchPerformative(ACLMessage.CONFIRM));
			if (msg != null) {
				currentParticipants.add(msg.getSender());
			}
		}

		@Override
		public int onEnd() {
			for (AID a : currentParticipants)
				System.out.println(myAgent.getLocalName()
						+ ": Found these participants: " + a.getName());
			// Start auction
			String[] genre1 = { "Impressionalism", "Portrait", "Painting" };
			Artifact art = new Artifact(0, "Mona Lisa", "Leonardo da Vinci", 1519,
					"Italy", genre1);
			myAgent.addBehaviour(new RunAuctionBehavoiur(art, 25000,
					15000));
			return super.onEnd();
		}

		@Override
		public boolean done() {
			return System.currentTimeMillis() > (startTime + WAIT_TIME_MILLIS);
		}
	}

	// --------------------------AUCTION BEHAVIOURS
	// BELOW---------------------------------//

	private class RunAuctionBehavoiur extends FSMBehaviour {

		private static final long serialVersionUID = -5449240349250130736L;
		private ArrayList<ACLMessage> responses;
		private Artifact saleItem;
		private int price;
		private int minimumprice;

		private final int BUYER_NOT_FOUND = 0;
		private final int BUYER_FOUND = 1;
		private final int MINIMUM_NOT_REACHED = 0;
		private final int MINIMUM_REACHED = 1;

		private final String STATE_AUCTION_START = "AUCTION_START";
		private final String STATE_SEND_PROPOSAL = "SEND_PROPOSAL";
		private final String STATE_LISTEN_TO_BIDDING = "LISTEN_TO_BIDDING";
		private final String STATE_AUCTION_CLOSED = "AUCTION_CLOSED";

		public RunAuctionBehavoiur(Artifact item, int marketvalue,
				int minimumprice) {
			price = (int) (marketvalue * 1.5);
			this.minimumprice = minimumprice;
			saleItem = item;
			responses = new ArrayList<ACLMessage>();

			registerFirstState(new AuctionStartBehaviour(), STATE_AUCTION_START);
			registerState(new SendProposalBehaviour(), STATE_SEND_PROPOSAL);
			registerState(new ListenToBiddingAndRespondBehaviour(),
					STATE_LISTEN_TO_BIDDING);
			registerLastState(new CloseAuctionBehaviour(), STATE_AUCTION_CLOSED);

			registerDefaultTransition(STATE_AUCTION_START, STATE_SEND_PROPOSAL);
			registerTransition(STATE_SEND_PROPOSAL, STATE_LISTEN_TO_BIDDING,
					MINIMUM_NOT_REACHED);
			registerTransition(STATE_SEND_PROPOSAL, STATE_AUCTION_CLOSED,
					MINIMUM_REACHED);
			registerTransition(STATE_LISTEN_TO_BIDDING, STATE_SEND_PROPOSAL,
					BUYER_NOT_FOUND);
			registerTransition(STATE_LISTEN_TO_BIDDING, STATE_AUCTION_CLOSED,
					BUYER_FOUND);

			scheduleFirst();
		}

		private class AuctionStartBehaviour extends OneShotBehaviour {

			private static final long serialVersionUID = -7548001839591300130L;

			@Override
			public void action() {
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
				try {
					msg.setContentObject(saleItem);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (AID aid : currentParticipants)
						msg.addReceiver(aid);
					myAgent.send(msg);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		private class SendProposalBehaviour extends OneShotBehaviour {

			private static final long serialVersionUID = 5961838568810065026L;
			private boolean minimumreached = false;

			@Override
			public void action() {
				price = (int) (price * 0.95);
				if (price >= minimumprice) {

					//System.out.println("New offer to send to participants: "
					//		+ price);
					bestOffer.price = price;
					
					ACLMessage msg = new ACLMessage(ACLMessage.CFP);
					try {
						msg.setContentObject(price);
					} catch (IOException e) {
						e.printStackTrace();
					}
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (AID aid : currentParticipants)
						msg.addReceiver(aid);
					myAgent.send(msg);
				} else {
					minimumreached = true;
				}
			}

			@Override
			public int onEnd() {
				if (!minimumreached) {
					//System.out
					//		.println("Minimum value was not reached, continuing auction");
					return MINIMUM_NOT_REACHED;
				} else {
					System.out.println(myAgent.getLocalName() + ": Minimum value was reached, closing auction.");
					bestOffer.price = 0;
					bestOffer.buyer = null;
					return MINIMUM_REACHED;
				}
			}
		}

		private class ListenToBiddingAndRespondBehaviour extends Behaviour {

			private static final long serialVersionUID = 1018138820141606339L;

			public ListenToBiddingAndRespondBehaviour() {
				responses.clear();
			}

			@Override
			public void action() {
				ACLMessage msg = myAgent
						.receive(MessageTemplate
								.MatchProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION));
				if (msg != null) {
					responses.add(msg);
				} else
					block();
			}

			@Override
			public boolean done() {
				return responses.size() == currentParticipants.size();
			}

			@Override
			public int onEnd() {

				ArrayList<AID> buyers = new ArrayList<AID>();

				for (ACLMessage msg : responses) {
					if (msg.getPerformative() == ACLMessage.PROPOSE) {
						if (msg.getContent().equals("ACCEPT")) {
							buyers.add(msg.getSender());
						}
					}
				}

				// Clear the response list
				responses.clear();

				//System.out
				//		.println("Number of buyers found this bidding round: "
				//				+ buyers.size());

				if (buyers.size() > 0) {
					// winner is index 0 of buyers
					ACLMessage msg = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					msg.addReceiver(buyers.get(0));
					bestOffer.buyer = buyers.get(0);
					myAgent.send(msg);

					// all other in the array will receive a reject
					msg = new ACLMessage(ACLMessage.REJECT_PROPOSAL);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
					for (int i = 1; i < buyers.size(); i++) {
						msg.addReceiver(buyers.get(i));
					}
					myAgent.send(msg);
					System.out
							.println(myAgent.getLocalName() + ": found a buyer for the artifact");
					return BUYER_FOUND;
				} else {
					//System.out
					//		.println("Curator did not find a buyer for the artifact");
					return BUYER_NOT_FOUND;
				}
			}
		}

		private class CloseAuctionBehaviour extends OneShotBehaviour {

			private static final long serialVersionUID = 2994648354079496671L;

			@Override
			public void action() {
				ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
				msg.setContent("AUCTION_CLOSED");
				msg.setProtocol(FIPANames.InteractionProtocol.FIPA_DUTCH_AUCTION);
				for (AID aid : currentParticipants)
					msg.addReceiver(aid);
				myAgent.send(msg);
				
				
				// Return to home with our lewt
				doMove(home);
			}
		}
	}
}
